using System;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using System.Globalization;

namespace Dida.Dxml
{
	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
	public class DidaObjectAttribute : Attribute
	{
		string tagName;
		Type compilerType = null;

		public string TagName
		{
			get { return tagName; }
			set { tagName = value; }
		}

		public Type Compiler
		{
			get { return compilerType; }
			set { compilerType = value; }
		}
	}

	public enum ReferencesOrder
	{
		GetReferencesOrder,
		DependenciesOrder,
	}

	public interface IXmlObjectCompiler
	{
		void Load(XmlReader reader, XmlCompiler compiler);

		/// <summary>
		/// Should return all objects that are referenced by this object.
		/// </summary>
		/// <returns>An array of <see cref = "CompilingObject">CompilingObjects</see>
		/// of the referenced objects.
		/// </returns>
		CompilingReference[] GetReferences();
		object CreateObject(XmlCompiler compiler, object[] resolvedReferences);

		ReferencesOrder ReferencesOrder
		{
			get;
		}

		/// <summary>
		/// Gets the (local, non qualified) name of this object
		/// </summary>
		string Name
		{
			get;
		}

		/// <summary>
		/// Gets whether this object is exported. If an object is exported, 
		/// it can be accessed from outside using it's name.
		/// </summary>
		bool Export
		{
			get;
		}

		Type ObjectType
		{
			get;
		}
	}

	public abstract class XmlObjectCompilerBase : IXmlObjectCompiler
	{
		string name;
		bool export;

		private void ReadName(XmlReader reader)
		{
			name = reader.GetAttribute("name");
			if (name == null)
				throw new XmlReaderException("No name attribute specified", reader);
		}

		private void ReadExport(XmlReader reader)
		{
			try
			{
				string exportStr = reader.GetAttribute("export");
				if (exportStr == null)
					export = false;
				else
					export = bool.Parse(exportStr);
			}
			catch (Exception err)
			{
				throw new XmlAttributeException(err, reader, "export");
			}
		}

		/// <summary>
		/// Reads the default attributes for this object. 
		/// </summary>
		/// <param name="reader"></param>
		/// <remarks>
		/// The default attributes are:
		/// <list type="bullet">
		/// <item>name</item>
		/// <item>export</item>
		/// </list>
		/// </remarks>
		protected void ReadDefAttributes(XmlReader reader)
		{
			ReadName(reader);
			ReadExport(reader);
		}

		protected void FinalizeNoContentElement(XmlReader reader)
		{
			if (reader.IsEmptyElement)
				reader.ReadStartElement();
			else
			{
				reader.ReadStartElement();
				reader.ReadEndElement();
			}
		}

		public abstract void Load(XmlReader reader, XmlCompiler compiler);
		public abstract CompilingReference[] GetReferences();
		public abstract object CreateObject(XmlCompiler compiler,
			object[] resolvedReferences);

		public string Name
		{
			get { return name; }
		}

		public bool Export
		{
			get { return export; }
		}

		public abstract Type ObjectType
		{
			get;
		}

		public virtual ReferencesOrder ReferencesOrder
		{
			get { return ReferencesOrder.GetReferencesOrder; }
		}

		protected static float ReadFloatAttribute(XmlReader reader,string attrName)
		{
			string str = reader.GetAttribute(attrName);
			if (str == null)
			{
				throw new XmlReaderException("No \"" + attrName + "\" attribute specified.", reader);
			}

			try
			{
				return float.Parse(str, NumberStyles.Float);
			}
			catch (Exception err)
			{
				throw new XmlAttributeException(err, reader, attrName);
			}
		}

		protected static string ReadStringAttribute(XmlReader reader, string attrName)
		{
			string str = reader.GetAttribute(attrName);
			if (str == null)
			{
				throw new XmlReaderException("No \"" + attrName + "\" attribute specified.", reader);
			}

			return str;
		}
	}

	public enum ContainerAddResult
	{
		/// <summary>
		/// The object was added to the container.
		/// </summary>
		Added,
		/// <summary>
		/// The object was not added to the container, so we can try to add it
		/// to a higher level container
		/// </summary>
		NotAdded,
		/// <summary>
		/// The object was not added to the container, but it shouldn't be added
		/// to eventual higher level containers.
		/// </summary>
		NotAddedBlocked,
	}

	public interface IContainerObjectCompiler : IXmlObjectCompiler
	{
		ContainerAddResult AddChild(CompilingObject child);
	}

	public abstract class ContainerObjectCompilerBase : XmlObjectCompilerBase, IContainerObjectCompiler
	{
		public abstract ContainerAddResult AddChild(CompilingObject child);
	}

	/// <summary>
	/// This interface can be implemented to handle references to external
	/// content (files). The simplest implementation is probably 
	/// <see cref = "ExternalFileLoader"/>, which loads files.
	/// </summary>
	public interface IExternalsLoader
	{
		// TODO: Comment this class.

		IExternalStream BeginStreamBlock(string name, XmlPositionMarker errorPos);
		void EndStreamBlock(IExternalStream s);
	}

	public interface IExternalStream
	{
		Stream OpenStream();
	}
}
